#include <assert.h>
#include <irrlicht.h>
#include "demo.h"
#include "fx_alyvuoto.h"


using namespace irr;
using namespace irr::core;
using namespace irr::scene;


// assumed fx rendering goes past the "end time" this many seconds:
const float alyvuoto_endTransitionLengthInSeconds = 1.0f;


static ISceneManager *sSceneManager = 0;
static ISceneNode *sEnemy = 0;
static ISceneNode *sKouru = 0;
static ISceneNode *sValo1, *sValo2= 0;

struct PosRot
{
    vector3df pos;
    vector3df rot; // euler angles used by irr
};

static array<PosRot> sPath;

// kouru            100
// path0...path29   0...29
// sika             102
// enemy            101
// - valo2          202
// valo1            201

bool AlyvuotoFx::init()
{
    if (!gIrrDevice || !gIrrDevice->getSceneManager())
        return false;

    //bool zipRes = gIrrDevice->getFileSystem()->addFileArchive("data_oldies_alyvuoto.zip", false, false, io::EFAT_ZIP);
    //assert(zipRes);

    sSceneManager = gIrrDevice->getSceneManager()->createNewSceneManager();

    ISceneManager *sceneManager = sSceneManager;

    sceneManager->loadScene("data/oldies/alyvuoto.irr");

    ISceneNode *light = sceneManager->getSceneNodeFromName("valo1");

    sEnemy = sceneManager->getSceneNodeFromName("enemy");
    sKouru = sceneManager->getSceneNodeFromName("kouru");
    sValo1 = sceneManager->getSceneNodeFromName("valo1");
    sValo2 = sceneManager->getSceneNodeFromName("valo2");
    ((ILightSceneNode *)sValo2)->getLightData().CastShadows = false;

    sceneManager->addCameraSceneNode();
    vector3df cameraPosition = light->getPosition();
    sceneManager->getActiveCamera()->setPosition(cameraPosition);
    sceneManager->getActiveCamera()->setTarget(sEnemy->getPosition());

    // pick path nodes
    {
        int nextNodeId = 0;
        bool more;
        do
        {
            more = false;
            ISceneNode *sn = sceneManager->getSceneNodeFromId(nextNodeId++);
            if (sn)
            {
                PosRot pr;
                pr.pos = sn->getPosition();
                pr.rot = sn->getRotation();
                sPath.push_back(pr);
                more = true;
            }
        } while (more);
    }

    // hack mesh & material for kouru
    assert(sKouru->getType() == scene::ESNT_MESH || sKouru->getType() == scene::ESNT_ANIMATED_MESH);
    IMeshSceneNode *kouruMSN = (IMeshSceneNode *)sKouru;
    IMesh *newKouruMesh = sceneManager->getMeshManipulator()->createMeshWithTangents(kouruMSN->getMesh());//, true, true, true, true);
    kouruMSN->setMesh(newKouruMesh);
    video::ITexture *kouruwNormalMap = sceneManager->getVideoDriver()->getTexture("data/oldies/alyvuoto/kouru0w_height.png");
    video::ITexture *kourufNormalMap = sceneManager->getVideoDriver()->getTexture("data/oldies/alyvuoto/kouru0f_height.png");
    sceneManager->getVideoDriver()->makeNormalMapTexture(kouruwNormalMap, 4.0f);
    sceneManager->getVideoDriver()->makeNormalMapTexture(kourufNormalMap, 16.0f);
    sKouru->setMaterialType(video::EMT_PARALLAX_MAP_SOLID);
    sKouru->getMaterial(0).setTexture(1, kouruwNormalMap);
    sKouru->getMaterial(1).setTexture(1, kourufNormalMap);
    sKouru->getMaterial(0).MaterialTypeParam = 1.0f / kouruwNormalMap->getSize().Width;
    sKouru->getMaterial(1).MaterialTypeParam = 1.0f / kourufNormalMap->getSize().Width;

    // hack mesh & material for enemy
    assert(sEnemy->getType() == scene::ESNT_MESH || sEnemy->getType() == scene::ESNT_ANIMATED_MESH);
    IMeshSceneNode *enemyMSN = (IMeshSceneNode *)sEnemy;
    IMesh *newEnemyMesh = sceneManager->getMeshManipulator()->createMeshWithTangents(enemyMSN->getMesh());//, true, true, true, true);
    IAnimatedMeshSceneNode *amsn = sceneManager->addAnimatedMeshSceneNode(new SAnimatedMesh(newEnemyMesh));
    //amsn->setPosition(sEnemy->getPosition());
    //amsn->setRotation(sEnemy->getRotation());
    sValo2->setParent(amsn);
    newEnemyMesh->drop();
    IShadowVolumeSceneNode *svsn = amsn->addShadowVolumeSceneNode();
    sEnemy->remove();
    sEnemy = amsn;
    video::ITexture *enemyNormalMap = sceneManager->getVideoDriver()->getTexture("data/oldies/alyvuoto/enemy_height.png");
    sceneManager->getVideoDriver()->makeNormalMapTexture(enemyNormalMap, 1.0f);
    sEnemy->setMaterialTexture(1, enemyNormalMap);
    sEnemy->setMaterialType(video::EMT_NORMAL_MAP_SOLID);
    sEnemy->getMaterial(0).MaterialTypeParam = 1.0f / enemyNormalMap->getSize().Width;

    mInitialized = true;
    return true;
}


static s32 sClampSplineIndex(s32 idx, s32 size)
{
	//return ( idx<0 ? size+idx : ( idx>=size ? idx-size : idx ) );  // loop
    if (idx < 0)
        idx = 0;
    //if (idx >= size)
    //    idx = size - 1;
    if (idx >= size)
        idx = size - 3 + idx % 3; // loop last 3 control points
    return idx;
}


// normalizedPosition should be time in range [0..1]
static core::vector3df sInterpolatePosition(const array<PosRot> &keys, float normalizedPosition, float tightness = 0.5f)
{
    core::vector3df result(0, 0, 0);

    const int pSize = (int)keys.size();
    if (pSize == 0)
        return result;

    // Spline-interpolate position
    // (Spline interpolation code is taken from Irrlicht's CSceneNodeAnimatorFollowSpline)

    const f32 dt = normalizedPosition * pSize;
    const f32 u = core::fract(dt);
    const s32 idx = core::floor32(dt); // % pSize; - disabled wrap-around...

    s32 idx0 = sClampSplineIndex(idx - 1, pSize);
    s32 idx1 = sClampSplineIndex(idx, pSize);
    s32 idx2 = sClampSplineIndex(idx + 1, pSize);
    s32 idx3 = sClampSplineIndex(idx + 2, pSize);
    const core::vector3df p0 = keys[idx0].pos;
    const core::vector3df p1 = keys[idx1].pos;
    const core::vector3df p2 = keys[idx2].pos;
    const core::vector3df p3 = keys[idx3].pos;

    // hermite polynomials
    const f32 h1 = 2.0f * u * u * u - 3.0f * u * u + 1.0f;
    const f32 h2 = -2.0f * u * u * u + 3.0f * u * u;
    const f32 h3 = u * u * u - 2.0f * u * u + u;
    const f32 h4 = u * u * u - u * u;

    // tangents
    const core::vector3df t1 = ( p2 - p0 ) * tightness;
    const core::vector3df t2 = ( p3 - p1 ) * tightness;

    // interpolated point
    result = p1 * h1 + p2 * h2 + t1 * h3 + t2 * h4;
    
    return result;
}



void AlyvuotoFx::render(double time, int timeMs)
{
    float t = (float)(time - mFxStartTime);

    vector3df pos0 = sInterpolatePosition(sPath, (float)((t - 0.4f) / (mFxEndTime - mFxStartTime)));
    vector3df pos = sInterpolatePosition(sPath, (float)(t / (mFxEndTime - mFxStartTime)));
    pos.Y -= 2.0f;
    sEnemy->setPosition(pos);
    f32 angle = atan2(pos.X - pos0.X, pos.Z - pos0.Z);
    sEnemy->setRotation(vector3df(0, (f32)(angle * 180 / core::PI), 0));

    // camera comes 1sec behind, a bit higher
    vector3df posc = sInterpolatePosition(sPath, (float)((t - 1.0f) / (mFxEndTime - mFxStartTime)));
    posc.Y += 2.0f;
    sSceneManager->getActiveCamera()->setPosition(posc);
    sSceneManager->getActiveCamera()->setTarget(pos); // target enemy

    sSceneManager->setShadowColor(video::SColor(0x88331155));
	sSceneManager->drawAll();
}


void AlyvuotoFx::deinit()
{
    sSceneManager->drop();
    mInitialized = false;
}
